// Copyright 2007 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.enterprise.connector.afyd;

import com.google.enterprise.connector.spi.RepositoryException;
import com.google.enterprise.connector.spi.Document;
import com.google.enterprise.connector.spi.SpiConstants;
import com.google.enterprise.connector.spi.Value;

import com.google.gdata.data.Entry;
import com.google.gdata.data.DateTime;
import com.google.gdata.data.PlainTextConstruct;
import com.google.gdata.data.TextContent;

import junit.framework.TestCase;

import java.util.List;
import java.util.LinkedList;
import java.io.File;

/**
 * This class is a test case that verifies several properties of the 
 * StatelessDocumentList implementation.
 * 
 * @author amsmith@google.com (Adam Smith)
 */
public class StatelessDocumentListTest extends TestCase {

  private LinkedList users;
  private LinkedList entries;
  private String propertiesFilename;
  private File propertiesFile;
  private StatelessDocumentList docList;
  private RiggedProvider provider;
  
  public void setUp() throws Exception {
    super.setUp();
    users = new LinkedList();
    entries = new LinkedList();
    propertiesFile = File.createTempFile("traversal-", ".properties");
    propertiesFilename = propertiesFile.getCanonicalPath();
    provider = new RiggedProvider();
    provider.setEntries(entries);
    docList = new StatelessDocumentList(users, propertiesFilename, provider);
  }
  
  public void tearDown() throws Exception {
    super.tearDown();
    propertiesFile.delete();
  }
  
  /**
   * In this test we ensure that the document list safely returns no documents
   * when the user list is empty.
   */
  public void testOkWithNoUsers() throws RepositoryException {
    // users is empty
    // entries is empty
    assertNull(docList.nextDocument());
    assertNull(docList.nextDocument()); // even when traversal is restarted
  }
  
  /**
   * In this test we ensure that the document list safely returns no documents
   * when the provider tells it there are no documents for any user despite
   * there being several users to track.
   */
  public void testOkWithNoEntries() throws RepositoryException {
    // there are some users
    users.add("a");
    users.add("b");
    users.add("c");
    users.add("d");
    // entries is empty (will be returned this way for each user)
    assertNull(docList.nextDocument());
    assertNull(docList.nextDocument()); // even when traversal is restarted
  }
  
  /**
   * This test ensures that the walk through the list of user names is not
   * affected by adding and removing entries including the current user.
   */
  public void testResumesAfterMissingUser() throws RepositoryException {
    // there are three users
    users.add("a");
    users.add("c");
    users.add("d");
    // each has an entry (the same one)
    entries.add(getEntryWithId("id"));
    
    docList.nextDocument(); // doc for a
    assertEquals("a", provider.getLastUser());
    docList.nextDocument(); // doc for c
    assertEquals("c", provider.getLastUser());
    docList.checkpoint();
    docList = null; // forget the old document list
    
    users.set(1, "b");
    docList = new StatelessDocumentList(users, propertiesFilename, provider);
    docList.nextDocument(); // doc for d
    assertEquals("d", provider.getLastUser());
  }
  
  /**
   * This tests that a users' checkpoints are persisted by checkpointing (not
   * just the current user).  
   */
  public void testCheckpointsArePersisted() throws RepositoryException {
    // there are two users
    users.add("a");
    users.add("b");
    // they each have a document
    entries.add(getEntryWithId("id"));
    
    provider.setNextCheckpoint("checkpoint for a");
    docList.nextDocument(); // doc for a
    provider.setNextCheckpoint("checkpoint for b");
    docList.nextDocument(); // doc for b
    docList.nextDocument(); // null indicating end of list
    docList.checkpoint();
    docList = null; // forget the old document list
    
    docList = new StatelessDocumentList(users, propertiesFilename, provider);
    docList.nextDocument(); // doc for a
    assertEquals("checkpoint for a", provider.getLastCheckpoint());
    docList.nextDocument(); // doc for b
    assertEquals("checkpoint for b", provider.getLastCheckpoint());
  }
  
  /**
   * This test ensures an entry from the provider eventually becomes a document
   * that represents it and that it gets returned through the list.
   */
  public void testDataFlowsThroughDocumentList()
      throws RepositoryException {
    // id starts with unicode nuclear symbol
    String nonceId = "\u9762" + System.currentTimeMillis();
    // one user
    users.add("a");
    // one entry
    entries.add(getEntryWithId(nonceId));
    
    Document doc = docList.nextDocument();
    assertNotNull(doc);
    assertTrue(hasId(doc, nonceId));
  }
  
  /** This factory method creates Entry objects with a specified id. */
  public static Entry getEntryWithId(String id) {
    Entry entry = new Entry();
    entry.setId(id);
    entry.setUpdated(DateTime.now());
    entry.addHtmlLink("http://0.0.0.0/" + id, "en", id);
    PlainTextConstruct construct = new PlainTextConstruct();
    construct.setText(id);
    TextContent content = new TextContent();
    content.setContent(construct);
    entry.setContent(content);
    return entry;
  }
  
  /** Check if a documentized entry from the above factory has a given id. */
  public static boolean hasId(Document doc, String id) {
    String docId = null;
    try {
       docId =
        Value.getSingleValueString(doc, SpiConstants.PROPNAME_DOCID);
    } catch (RepositoryException re) {
      return false;
    }
    return docId.equals(id);
  }
  
  /**
   * This class is a minimal FeedEntryProvider with instrumentation.
   * 
   * The list passed to setResultList may be changed at any time outside of this
   * class and the new contents will be reflected in the next call to the
   * getOrderedEntriesForUsers method because this class holds but a reference
   * to the resultList list.
   */
  public static class RiggedProvider implements FeedEntryProvider {
    private LinkedList entries;
    private String lastUser;
    private String lastCheckpoint;
    public static final String DUMMY_CHECKPOINT = "whatever, this is fake...";
    private String nextCheckpoint;
    
    public String getCheckpointForEntry(Entry entry) {
      if (nextCheckpoint != null) {
        String toReturn = nextCheckpoint;
        nextCheckpoint = null;
        return toReturn;
      } else {
        return DUMMY_CHECKPOINT;
      }
    }
    
    public  List getOrderedEntriesForUser(String user, String checkpoint) {
      lastUser = user;
      lastCheckpoint = checkpoint;
      return (List) entries.clone();
    }
    
    public String getLastUser() {
      return lastUser;
    }
    
    public String getLastCheckpoint() {
      return lastCheckpoint;
    }
    
    public void setEntries (LinkedList entries) {
      this.entries = entries;
    }
    
    public void setNextCheckpoint (String nextCheckpoint) {
      this.nextCheckpoint = nextCheckpoint;
    }
  }
}
